function trArr(input_params) {
	
	/**
	 * Loosely modeled on jquery.parsequery.js by Michael Manning (http://actingthemaggot.com)
	 **/
	trArrParseQuery = function(qs) {
		var q = (typeof qs === 'string'?qs:window.location.search);
		var params = {};
		jQuery.each(q.match(/^\??(.*)$/)[1].split('&'),function(i,p){
			p = unescape(p).replace(/\+/g,' ').replace(/\]/g,'');
			p = p.split('=');
			var keys = p[0].split('[');
			var value = p[1];
			var depth = keys.length;
			if (depth == 1) {
				// actually shouldn't happen, should always have at least two levels
				if (params[keys[0]] == undefined) {
					params[keys[0]] = {};
				}
				params[keys[0]][value] = true;
			}
			if (depth == 2) {
				if (params[keys[0]] == undefined) {
					params[keys[0]] = {};
				}
				if (params[keys[0]][keys[1]] == undefined) {
					params[keys[0]][keys[1]] = {};
				}
				params[keys[0]][keys[1]][value] = true;
			}
			if (depth == 3) {
				if (params[keys[0]] == undefined) {
					params[keys[0]] = {};
				}
				if (params[keys[0]][keys[1]] == undefined) {
					params[keys[0]][keys[1]] = {};
				}
				if (params[keys[0]][keys[1]][keys[2]] == undefined) {
					params[keys[0]][keys[1]][keys[2]] = {};
				}
				params[keys[0]][keys[1]][keys[2]][value] = true;
			}
		});
		return params;
	}
	
	// ensure this is called as constructor
	
	if (!(this instanceof trArr)) {
		return new trArr(input_params);
	}
	
	// create some display containers if the page builder has not already placed them for us
	if (jQuery('#arrivals_display_area').length == 0) {
		jQuery('body').prepend('<div id="arrivals_display_area"></div>');
	}
	if (jQuery('#arrivals_log_area').length == 0) {
		jQuery('body').prepend('<div id="arrivals_log_area"></div>');
	}
	
	this.input_params = input_params;
	
	// we should set up some defaults here
	this.query_params = trArrParseQuery(this.configString);
	
	this.ready = new jQuery.Deferred();
	
	this.is_ready = function() {
		return this.ready;
	}
	
	this.updaters = function() {
		return this.updater_array;
	}
	
	this.processAgencyRequests = function(arrivals_object,agency,callback) {
		var agency_js_name = agency.replace('-',"");
		jQuery.when(jQuery.ajax({
		  url: "assets/js/trArrAgency"+agency_js_name+".js",
		  dataType: 'script',
		  async: false,
		  success: function() {
		  	jQuery('#arrivals_log_area').append("Processing requests for "+agency+"<br>");
		  	var function_name = "trArrAgency"+agency_js_name+"MakeServiceRequests";
		  	window[function_name](arrivals_object.query_params.stop[agency],arrivals_object.query_params.options,trStopCache(),arrivals_object.service_requests);
		  },
		  error: function() {
		  	jQuery('#arrivals_log_area').append("<font color='red'>Error processing requests for "+agency+"</font><br>");
		  }
		})).done(function(data) {
			var agency = arrivals_object.nextServiceRequestAgency(arrivals_object);
			if (agency == undefined) {
				callback(arrivals_object);
			} else {
				arrivals_object.processAgencyRequests(arrivals_object,agency,callback);
			}
		}).fail(function(data) {
			var agency = arrivals_object.nextServiceRequestAgency(arrivals_object);
			if (agency == undefined) {
				callback(arrivals_object);
			} else {
				arrivals_object.processAgencyRequests(arrivals_object,agency,callback);
			}
		});
	}
		
	this.nextServiceRequestAgency = function(arrivals_object) {

		for (var agency in arrivals_object.query_params.stop) {
			if (arrivals_object.service_request_agencies_pending[agency] == undefined) {
				arrivals_object.service_request_agencies_pending[agency] = true;
				return agency;
			}
		}
		return undefined;
	}
	
	this.nextUpdaterRequest = function(arrivals_object) {

		for (var service in arrivals_object.service_requests) {
			if (arrivals_object.updater_creation_pending[service] == undefined) {
				arrivals_object.updater_creation_pending[service] = true;
				return service;
			}
		}
		return undefined;
	}
	
	this.createUpdaterObjects = function(arrivals_object, service, callback) {
		jQuery.when(jQuery.ajax({
		  url: "assets/js/trArrService"+service+".js",
		  dataType: 'script',
		  async: false,
		  success: function() {
		  	jQuery('#arrivals_log_area').append("Creating updaters for "+service+"<br>");
		  	var function_name = "trArrService"+service+"CreateUpdaters";
		  	window[function_name](arrivals_object,arrivals_object.service_requests[service],arrivals_object.updater_array);
		  },
		  error: function() {
		  	jQuery('#arrivals_log_area').append("<font color='red'>Error creating updaters for "+service+"</font><br>");
		  }
		})).done(function(data) {
			var service = arrivals_object.nextUpdaterRequest(arrivals_object);
			if (service == undefined) {
				callback(arrivals_object);
			} else {
				arrivals_object.createUpdaterObjects(arrivals_object,service,callback);
			}
		}).fail(function(data) {
			var service = arrivals_object.nextUpdaterRequest(arrivals_object);
			if (service == undefined) {
				callback(arrivals_object);
			} else {
				arrivals_object.createUpdaterObjects(arrivals_object,service,callback);
			}
		});
	}
	
	this.mergeArrivals = function() {
		var arrivals = [];
  	for (var i = 0; i < this.updater_array.length; i++) {
  		var updater_arrivals = this.updater_array[i].arrivals();
  		for (var j = 0; j < updater_arrivals.length; j++) {
  			arrivals.push(updater_arrivals[j]);
  		}
  	}
  	return arrivals;
	}
	  
	// create and populate stop information cache, the build structure of agency service requests
	trStopCache().checkCached(this,this.query_params.stop,	function (arrivals_object) {
		arrivals_object.service_requests = {};
		arrivals_object.updater_array = [];
		arrivals_object.service_request_agencies_pending = {};
		arrivals_object.updater_creation_pending = {};
		
		
		for (var agency in arrivals_object.query_params.stop) {
			arrivals_object.service_request_agencies_pending[agency] = undefined;
		}
			
		var agency = arrivals_object.nextServiceRequestAgency(arrivals_object);
		if (agency != undefined) {
			arrivals_object.processAgencyRequests(arrivals_object,agency,function(arrivals_object) {
				jQuery('#arrivals_log_area').append("Request processing complete.<br><br>");
				
				jQuery('#arrivals_log_area').append("Create arrivals updater objects<br>");
				for (var service in arrivals_object.service_requests) {
					arrivals_object.updater_creation_pending[service] = undefined;
				}
				
				var service = arrivals_object.nextUpdaterRequest(arrivals_object);
				arrivals_object.createUpdaterObjects(arrivals_object, service, function(arrivals_object) {
					// wait 10 seconds for first arrivals to load and then tell 'em we're done
					jQuery('#arrivals_log_area').append("<br>Wait 10 seconds for first set of arrivals<br><br>");
					setTimeout(function(){
						
						// first time through
						var displayCallCount = 0;
						cyclesSinceUpdate = 0;
						jQuery('#arrivals_log_area').css("display","none");
						var arrivals = arrivals_object.mergeArrivals();
						arrivals_object.input_params.displayCallback(arrivals,displayCallCount,cyclesSinceUpdate);
						var last_update_cycle = 0;
						
						// now iterate forever
						setInterval(function(){
							displayCallCount++;
							if (displayCallCount % input_params.queueUpdateMultiple == 0) {
								arrivals = arrivals_object.mergeArrivals();
								last_update_cycle = displayCallCount;
							}
							cyclesSinceUpdate = displayCallCount - last_update_cycle;
							arrivals_object.input_params.displayCallback(arrivals,displayCallCount,cyclesSinceUpdate);
						},arrivals_object.input_params.displayInterval);
						
					},10*1000);
				});
			});
		}
			
	});
		

	
}


